package com.gegong.imr.client.vbox.cmd;

import com.gegong.imessage.dto.TVmTask;
import com.gegong.imessage.utils.MacAddressUtils;
import com.gegong.imr.client.vbox.OSTypeId;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.reflections.ReflectionUtils;
import org.virtualbox_5_2.*;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import static org.reflections.ReflectionUtils.*;


@Slf4j
public class CopyVMCmd extends AbstractVBoxCmd {
    public CopyVMCmd(@NonNull VirtualBoxManager virtualBoxManager, @NonNull String targetVm) {
        super(virtualBoxManager, targetVm);
    }

    @Override
    public Progress execute(Object... params) {
        try {
            log.info("复制虚拟机{}", getTargetVm());
            TVmTask task = (TVmTask) params[0];
            IVirtualBox virtualBox = getVirtualBoxManager().getVBox();
            IMachine parent = virtualBox.findMachine(params[1].toString());

            String newMachineName = task.getVirtualName();
            log.info("创建虚拟机...");

            IMachine machine = virtualBox.createMachine(null, newMachineName, parent.getGroups(), parent.getOSTypeId(), null);
            copyVmSettings(parent, machine, Sets.newHashSet("settingsFilePath", "name", "groups", "osTypeId"));
            copyNetworkSettings(parent, machine, true);
            copyAudioAdapterSettings(parent.getAudioAdapter(), machine.getAudioAdapter(), null);
            copyControllers(parent, machine);
            copyBootOrderSetting(parent, machine);
            machine.getNetworkAdapter(0L).setMACAddress(task.getVirtualMac());
            machine.setCPUIDLeaf(1L, 0L, 67301L, 1050624L, 10019837L, 3219913727L);
            machine.saveSettings();
            virtualBox.registerMachine(machine);
            log.info("虚拟机创建完毕并注册到VBoxManager");

//            log.info("复制虚拟机设置...");
//            IProgress progress = parent.cloneTo(virtualBox.findMachine(newMachineName), CloneMode.MachineState, null);
//            progress.waitForCompletion(TimeOuts.NONE);
//            log.info("虚拟机设置复制完成");
//            log.info("新虚拟机存储控制器:" + machine.getStorageControllers());

            copyMediums(parent, machine);

        } catch (Exception e) {
            progress.resultCode = Progress.RESULT_FAILURED;
            progress.errorInfo = e;
        } finally {
            progress.completed = true;
        }
        return progress;
    }

    @Override
    public String getName() {
        return "复制虚拟机";
    }


    private void copyBootOrderSetting(IMachine source, IMachine target) {
        log.info("拷贝虚拟机启动顺序设置");
        for (long i = 1; i <= 4; i++) {
            target.setBootOrder(i, source.getBootOrder(i));
        }
    }

    private void copyVmSettings(IMachine source, IMachine target, Set<String> excludes) {
        Set<Method> setters = getAllMethods(IMachine.class,
                withModifier(Modifier.PUBLIC), withPrefix("set"), withParametersCount(1));
        setters.forEach(setterMethod -> {
            if (Objects.isNull(excludes) || excludes.stream().noneMatch(fieldName -> setterMethod.getName().substring(3).toLowerCase().equals(fieldName.toLowerCase())))
                getAllMethods(source.getClass(), withName("g" + setterMethod.getName().substring(1))).forEach(getMethod -> {
                    try {
                        log.debug("拷贝虚拟机设置{}", getMethod.getName().substring(3));
                        setterMethod.invoke(target, getMethod.invoke(source));
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }

                });

        });

        log.info("拷贝ExtraDatas");
        //拷贝ExtraData
        source.getExtraDataKeys().forEach(edk -> {
            String extraData;
            log.info("拷贝ExtraData-{}", extraData = source.getExtraData(edk));
            target.setExtraData(edk, extraData);

        });

        // source.getcpuid
    }


    private void copyNetworkAdapterSetting(INetworkAdapter source, INetworkAdapter target, Set<String> excludes, boolean resetMac) {
        log.info("忽略的网络适配器设置{}", excludes);
        Set<Method> setters = getAllMethods(INetworkAdapter.class,
                withModifier(Modifier.PUBLIC), withPrefix("set"), withParametersCount(1));
        setters.forEach(setterMethod -> {
            if (Objects.isNull(excludes) || excludes.stream().noneMatch(fieldName -> setterMethod.getName().substring(3).toLowerCase().equals(fieldName.toLowerCase())))
                getAllMethods(source.getClass(), withName("g" + setterMethod.getName().substring(1))).forEach(getMethod -> {
                    try {
                        log.debug("拷贝网络适配器设置{}", getMethod.getName().substring(3));
                        setterMethod.invoke(target, getMethod.invoke(source));
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }

                });

        });
        String newMac;
        log.info("设置网卡mac地址为{}", newMac = MacAddressUtils.random());
        if (resetMac) target.setMACAddress(newMac);
    }


    private void copyNetworkSettings(IMachine source, IMachine target, boolean resetAllNetworkAdapterMac) {
        try {
            source.getNetworkAdapter(1000L);
        } catch (Exception e) {
            int networkAdapterCount = Integer.parseInt(e.getMessage().substring(e.getMessage().indexOf("total ") + "total ".length(), e.getMessage().indexOf("adapters")).trim());

            for (long i = 0; i < networkAdapterCount; i++) {
                log.info("拷贝{}插槽上的网络适配器设置", i);
                copyNetworkAdapterSetting(source.getNetworkAdapter(i), target.getNetworkAdapter(i), null, resetAllNetworkAdapterMac);

            }
        }


    }


    private void copyAudioAdapterSettings(IAudioAdapter source, IAudioAdapter target, List<String> excludes) {
        log.info("忽略的音频适配器设置{}", excludes);
        Set<Method> setters = getAllMethods(IAudioAdapter.class,
                withModifier(Modifier.PUBLIC), withPrefix("set"), withParametersCount(1));
        setters.forEach(setterMethod -> {
            if (Objects.isNull(excludes) || excludes.stream().noneMatch(fieldName -> setterMethod.getName().substring(3).toLowerCase().equals(fieldName.toLowerCase())))
                getAllMethods(source.getClass(), withName("g" + setterMethod.getName().substring(1))).forEach(getMethod -> {
                    try {
                        log.debug("拷贝音频适配器设置{}", getMethod.getName().substring(3));
                        setterMethod.invoke(target, getMethod.invoke(source));
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }

                });

        });
    }


    private void copyControllers(IMachine source, IMachine target) {
        source.getStorageControllers().forEach(sc -> {
            log.info("拷贝存储控制器{}", sc.getName());
            target.addStorageController(sc.getName(), sc.getBus());
        });

        source.getUSBControllers().forEach(usbc -> {
            log.info("拷贝usb控制器{}", usbc.getName());
            target.addUSBController(usbc.getName(), usbc.getType());

        });
    }

    private void copyMediums(IMachine source, IMachine target) {
        log.info("拷贝媒体文件...");
        source.getStorageControllers().forEach(sc -> {
            log.info("拷贝存储控制器{}上的媒体文件", sc.getName());
            source.getMediumAttachmentsOfController(sc.getName()).forEach(sourceMediumAttachment -> {
                log.info("复制媒体文件");
                IMedium sourceMedium = sourceMediumAttachment.getMedium();
                File settingsFilePath = new File(target.getSettingsFilePath());
                String vdiFilePathString = settingsFilePath.getParentFile().getAbsolutePath().replaceAll("\\\\", "/") + "/" + target.getName() + ".vdi";
                IMedium targetMedium = getVirtualBox().createMedium(sourceMedium.getFormat(), vdiFilePathString, AccessMode.ReadWrite, sourceMedium.getDeviceType());
                IProgress progress = sourceMedium.cloneToBase(targetMedium, sourceMedium.getVariant());
                progress.waitForCompletion(TimeOuts.NONE);


                log.info("将新创建的媒体附加到设备上");
                ISession session = getVirtualBoxManager().getSessionObject();
                target.lockMachine(session, LockType.VM);
                try {
                    IMachine sessionMachine = session.getMachine();
                    sessionMachine.attachDevice(sc.getName(), 0, 0, targetMedium.getDeviceType(), targetMedium);
                    sessionMachine.saveSettings();

                } finally {
                    session.unlockMachine();
                }
            });

        });


    }
}
